package server;

import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.Observable;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 *
 * @author Flick
 */
public class OurServer extends Observable {

    private ArrayList clientOutputStreams;
    private static final int DEFAULT_PORT = 5021;
    private static OurServer instance;
    private ClientLogger cLogger;

    public static void main(String[] args) {
        Thread tGui = new Thread(new ServerGUI());
        tGui.run();
        new OurServer().go();
    }

    private OurServer() {
    }

    /**
     * 
     * @return
     */
    public static OurServer getInstance() {
        if (instance == null) {
            instance = new OurServer();
        }
        return instance;
    }

    private void go() {
        clientOutputStreams = new ArrayList();
        cLogger = new ClientLogger();
        try {
            ServerSocket serverSock = new ServerSocket(DEFAULT_PORT);

            while (true) {
                Socket clientSocket = serverSock.accept();
                PrintWriter writer = new PrintWriter(clientSocket.getOutputStream());
                clientOutputStreams.add(writer);

                ClientHandler ch = new ClientHandler(clientSocket);
                cLogger.addChangeListener(ch);
                Thread t = new Thread(ch);
                t.start();
                System.out.println("got a connection");
            }

        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("error no connection");
        }
    }

    private class ClientLogger {

        private ArrayList<String> loggedIn;
        private ArrayList<ChangeListener> listeners;

        /**
        Constructs a blank clientlogger.
         */
        public ClientLogger() {
            loggedIn = new ArrayList<String>();
            listeners = new ArrayList<ChangeListener>();
        }

        public void add(String s) {
            loggedIn.add(s);
            // Notify all observers of the change to the invoice
            ChangeEvent event = new ChangeEvent(this);
            for (ChangeListener listener : listeners) {
                listener.stateChanged(event);
            }
        }

        public void remove(String s) {
            loggedIn.remove(s);
            // Notify all observers of the change to the invoice
            ChangeEvent event = new ChangeEvent(this);
            for (ChangeListener listener : listeners) {
                listener.stateChanged(event);
            }
        }

        public ArrayList<String> getLoggedIn() {
            return loggedIn;
        }

        /**
        Adds a change listener to the invoice.
        @param listener the change listener to add
         */
        public void addChangeListener(ChangeListener listener) {
            listeners.add(listener);
        }

        private void removeChangeListener(ClientHandler listener) {
            listeners.remove(listener);
        }
    }

    /**
     * 
     */
    private class ClientHandler implements Runnable, ChangeListener {

        private BufferedReader reader;
        private Socket sock;
        private PrintWriter writer;
        private String clientName,  response;
        private boolean connected = true;
        private Timer timer = new Timer();
        private final static int DISCONNECT_TIME = 10000;   //considered disconnected after 10sec

        public ClientHandler(Socket clientSocket) {
            try {
                sock = clientSocket;
                writer = new PrintWriter(sock.getOutputStream());
                InputStreamReader isReader = new InputStreamReader(sock.getInputStream());
                reader = new BufferedReader(isReader);
            } catch (Exception ex) {
                ex.printStackTrace();
                System.out.println("error clienthandler");
            }
        }

        public void run() {
            try {
                while ((clientName = reader.readLine()) == null) {
                    System.out.println("waiting");
                }
                System.out.println("read" + clientName);
                if (cLogger.getLoggedIn().contains(clientName)) {
                    writer.println("AlreadyLoggedIn");
                    System.out.println("sending already logged in");
                    writer.flush();
                } else {
                    writer.println("Successful");
                    System.out.println("Sending successful");
                    writer.flush();
                    cLogger.add(clientName);
                }
            } catch (Exception ex) {
                //ex.printStackTrace();
                System.out.println("catching an exception while trying to log in");
            }
            try {

                while (connected) {
                    while ((response = reader.readLine()) == null) {
                        timer.schedule(new DisconnectTask(), DISCONNECT_TIME);
                        System.out.println("reads null");
                    }
                    timer.cancel();
                    if (response.equals("Logging Out Now")) {
                        System.out.println("got logout request");
                        disconnect();
                    } else if (response.equals("Requesting Client List")) {
                        sendLoggedIn();
                        System.out.println("got client list request");
                    }
                }
            } catch (Exception ex) {
                //ex.printStackTrace();
                System.out.println("catching an exception (in clienthandler.run)");
                disconnect();
            }
        }

        public void disconnect() {
            System.out.println("Disconnecting a client: " + clientName);
            connected = false;
            cLogger.remove(clientName);
            cLogger.removeChangeListener(this);
            try {
                reader.close();
                writer.close();
                sock.close();
            } catch (IOException ex) {
                Logger.getLogger(OurServer.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        public void sendLoggedIn() {
            writer.println("Starting To Send");
            ArrayList<String> temp = cLogger.getLoggedIn();
            System.out.println("state changed: sending" + temp);
            for (String s : temp) {
                writer.println(s);
            }
            writer.println("End Of List");
            writer.flush();
        }

        public void stateChanged(ChangeEvent e) {
            sendLoggedIn();
        }

        private class DisconnectTask extends TimerTask {

            public void run() {
                disconnect();
            }
        }
    }
}

